import { parse } from "regexparam";
import { get as getVal, writable, derived } from "svelte/store";
import { user$ } from "@store/entity/User";
import { alertError, openSpinner, closeSpinner } from "@store/global";
import { canUse } from "./checker";


export type JSONValue = string | number | boolean | JSONObject | JSONArray;

export type JSONObject = {
  [k: string]: JSONValue;
};

export type JSONArray = Array<JSONValue>;

export const jsonPrimitiveTypes = ["string", "integer", "boolean", "number", "array", "object"] as const;
export type JSONDataType = typeof jsonPrimitiveTypes[number];

export type Nullable<T> = T | null;

// export const isLoading = writable(false);

export const httpMethods = ["GET", "POST", "PUT", "DELETE"] as const;
export type HttpMethod = typeof httpMethods[number];

export const paramPrimitiveTypes = [
  "string",
  "integer",
  "number",
  "boolean",
] as const;
export type ParamDataType = typeof paramPrimitiveTypes[number];

export const httpHeaders = [
  "Accept",
  "Accept-CHNon-Standard",
  "Accept-Charset",
  "Accept-Features",
  "Accept-Encoding",
  "Accept-Language",
  "Accept-Ranges",
  "Access-Control-Allow-Credentials",
  "Access-Control-Allow-Origin",
  "Access-Control-Allow-Methods",
  "Access-Control-Allow-Headers",
  "Access-Control-Max-Age",
  "Access-Control-Expose-Headers",
  "Access-Control-Request-Method",
  "Access-Control-Request-Headers",
  "Age",
  "Allow",
  "Alternates",
  "Authorization",
  "Cache-Control",
  "Connection",
  "Content-Encoding",
  "Content-Language",
  "Content-Length",
  "Content-Location",
  "Content-MD5",
  "Content-Range",
  "Content-Security-Policy",
  "Content-Type",
  "Cookie",
  "DNT",
  "Date",
  "ETag",
  "Expect",
  "Expires",
  "From",
  "Host",
  "If-Match",
  "If-Modified-Since",
  "If-None-Match",
  "If-Range",
  "If-Unmodified-Since",
  "Last-Event-ID",
  "Last-Modified",
  "Link",
  "Location",
  "Max-Forwards",
  "Negotiate",
  "Origin",
  "Pragma",
  "Proxy-Authenticate",
  "Proxy-Authorization",
  "Range",
  "Referer",
  "Retry-After",
  "Sec-Websocket-Extensions",
  "Sec-Websocket-Key",
  "Sec-Websocket-Origin",
  "Sec-Websocket-Protocol",
  "Sec-Websocket-Version",
  "Server",
  "Set-Cookie",
  "Set-Cookie2",
  "Strict-Transport-Security",
  "TCN",
  "TE",
  "Trailer",
  "Transfer-Encoding",
  "Upgrade",
  "User-Agent",
  "Variant-Vary",
  "Vary",
  "Via",
  "Warning",
  "WWW-Authenticate",
  "X-Content-Duration",
  "X-Content-Security-Policy",
  "X-DNSPrefetch-Control",
  "X-Frame-Options",
  "X-Requested-With",
] as const;
export type HttpHeader = typeof httpHeaders[number];

export const makeDefault = (t: ParamDataType) => {
  switch (t) {
    case "string":
      return "";
    case "boolean":
      return false;
    default:
      return 0;
  }
};

export type BodyFormat = "none" | "json" | "raw";
export const bodyFormats = ["none", "json", "raw"] as const;

export const ToUrlSearchParams = (v: object): URLSearchParams => {
  let r: Record<string, string> = Object.keys(v).reduce((o, k) => {
    o[k] = v[k].toString()
    return o
  }, {})
  return new URLSearchParams(r);
}

export const ToUrlSearchParamsStrict = (v: JSONObject): URLSearchParams => {
  let r: Record<string, string> = Object.keys(v).reduce((o, k) => {
    o[k] = v[k].toString()
    return o
  }, {})
  return new URLSearchParams(r);
}

const defaultHeaders = {
  "Content-Type": "application/json",
};

const getAuthHeaders = () => {
  return {
    ...defaultHeaders,
    "x-mock-id": getVal(user$)?.mock_id,
  };
};

const getNoAuthHeaders = () => {
  return {
    ...defaultHeaders,
  };
};

export type ApiResult<T> = {
  status: string;
  code?: number;
  error?: string;
  data?: T;
};

export type HttpOptions = {
  showSpinner?: boolean;
  timeout?: number;
  json?: boolean;
  auth?: boolean;
  checker?: <T>(o: ApiResult<T>) => T,
};

interface EntityResponse<T> extends Response {
  parsedBody?: ApiResult<T>;
}

const defaultHttpOptions = {
  showSpinner: false,
  timeout: 5 * 1000,
  json: true,
  auth: true,
  checker: <T>(jsonData: ApiResult<T>) => {
    let pass = jsonData.status === "ok"
    if (!pass) {
      throw new Error(`${jsonData.status}:${jsonData.error}`);
    }
    return jsonData.data;
  }
};

export async function http<T>(
  request: RequestInfo,
  options: HttpOptions
): Promise<T> {
  options.showSpinner && openSpinner("请求中...");
  const controller = new AbortController();
  const id = setTimeout(() => {
    controller.abort();
    options.showSpinner && closeSpinner();
  }, options.timeout);
  let result: T;
  try {
    const response: EntityResponse<T> = await fetch(request, {
      signal: controller.signal,
    });
    if (!response.ok) {
      if (response.status != 400) {
        console.log("response=>", response);
        throw new Error(response.statusText);
      }
    }
    if (options.json) {
      response.parsedBody = await response.json();
      const jsonData = response.parsedBody;
      if (options.checker) {
        result = options.checker(jsonData);
      }
      // if (jsonData.status != "ok") {
      //   // todo: alert to user
      //   console.log("jsonData=>", jsonData);
      //   throw new Error(`${jsonData.status}:${jsonData.error}`);
      // }
      // result = jsonData.data;
    } else {
      const textData = await response.text();
      result = <T>(textData as unknown);
    }
  } catch (ex: any) {
    alertError(ex.message);
    if (ex.name === "AbortError") {
      //超时
      console.warn("请求超时!");
    }
    throw ex;
  } finally {
    clearTimeout(id);
    options.showSpinner && closeSpinner();
  }
  return result;
}

export async function get<T>(
  path: string,
  searchParams?: Nullable<URLSearchParams>,
  options: HttpOptions = defaultHttpOptions,
  headers?: {},
  args: RequestInit = { method: "get" }
): Promise<T> {
  const _options = {
    ...defaultHttpOptions,
    ...options,
  };
  const _headers = {
    ...(_options.auth ? getAuthHeaders() : getNoAuthHeaders()),
    ...headers,
  };
  const url = new URL(path);
  if (canUse(searchParams)) {
    url.search = searchParams.toString();
  }
  return await http<T>(
    new Request(url.toString(), {
      ...args,
      headers: _headers,
    }),
    _options
  );
}

export async function post<T>(
  path: string,
  body?: any,
  searchParams?: Nullable<URLSearchParams>,
  options: HttpOptions = defaultHttpOptions,
  headers?: {},
  args: RequestInit = {
    method: "post",
    body: canUse(body) ? JSON.stringify(body) : undefined,
  }
): Promise<T> {
  const _options = {
    ...defaultHttpOptions,
    ...options,
  };
  const _headers = {
    ...(_options.auth ? getAuthHeaders() : getNoAuthHeaders()),
    ...headers,
  };
  const url = new URL(path);
  if (canUse(searchParams)) {
    url.search = searchParams.toString();
  }
  return await http<T>(
    new Request(url.toString(), {
      ...args,
      headers: _headers,
    }),
    _options
  );
}

export async function postPlain(
  path: string,
  bodyText: string,
  searchParams?: Nullable<URLSearchParams>,
  headers?: {},
  options: HttpOptions = defaultHttpOptions,
  args: RequestInit = { method: "post", body: bodyText }
): Promise<string> {
  const _options = {
    ...defaultHttpOptions,
    ...options,
  };
  const _headers = {
    ...(_options.auth ? getAuthHeaders() : getNoAuthHeaders()),
    ...headers,
    "Content-Type": "text/plain",
  };
  const url = new URL(path);
  if (canUse(searchParams)) {
    url.search = searchParams.toString();
  }
  return await http<string>(
    new Request(url.toString(), {
      ...args,
      headers: _headers,
    }),
    _options
  );
}

export async function put<T>(
  path: string,
  body?: any,
  searchParams?: Nullable<URLSearchParams>,
  options: HttpOptions = defaultHttpOptions,
  headers?: {},
  args: RequestInit = {
    method: "put",
    body: canUse(body) ? JSON.stringify(body) : undefined,
  }
): Promise<T> {
  const _options = {
    ...defaultHttpOptions,
    ...options,
  };
  const _headers = {
    ...(_options.auth ? getAuthHeaders() : getNoAuthHeaders()),
    ...headers,
  };
  const url = new URL(path);
  if (canUse(searchParams)) {
    url.search = searchParams.toString();
  }
  return await http<T>(
    new Request(url.toString(), {
      ...args,
      headers: _headers,
    }),
    _options
  );
}

export async function del<T>(
  path: string,
  searchParams?: Nullable<URLSearchParams>,
  options: HttpOptions = defaultHttpOptions,
  headers?: {},
  args: RequestInit = { method: "delete" }
): Promise<T> {
  const _options = {
    ...defaultHttpOptions,
    ...options,
  };
  const _headers = {
    ...(_options.auth ? getAuthHeaders() : getNoAuthHeaders()),
    ...headers,
  };
  const url = new URL(path);
  if (canUse(searchParams)) {
    url.search = searchParams.toString();
  }
  return await http<T>(
    new Request(url.toString(), {
      ...args,
      headers: _headers,
    }),
    _options
  );
}

export const parsePathParamKeys = (url: string): string[] => {
  return parse(url).keys;
};
export const parseQueryParamKeys = (url: string): string[] => {
  const qsPosition = url.indexOf("?");
  if (qsPosition === -1) {
    return [];
  }
  const querystring = url.substring(qsPosition);
  const urlSearchParams = new URLSearchParams(querystring);
  return Array.from(urlSearchParams.keys());
};

export type Paged<T> = {
  rows: T[];
  total: number
}